package org.gwt.grin.client;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.gwt.grin.client.commands.Command;
import org.gwt.grin.client.commands.ResetFeatureCommand;
import org.gwt.grin.client.util.Graphics;

/**
 * Represents a feature. A feature is a thing that presents some sort of UI. A
 * segment presents some number of features, and features can be shared between
 * segments.
 * 
 * @author Bill Foote (http://jovial.com)
 * @author ggeorg
 */
public abstract class Feature implements Node {

	protected Show show;

	/**
	 * The name of this feature within the show.
	 */
	protected final String name;

	private int activateCount = 0;
	private int setupCount = 0;

	private boolean changed = false;

	public Feature(String name) {
		this.name = name;
	}

	public Show getShow() {
		return show;
	}

	// package-private
	void setShow(Show show) {
		this.show = show;
	}

	/**
	 * All public features have a name. Private features might or might not have
	 * a name; if they do, it's just for debugging.
	 * 
	 * @return the nameof this feature, or {@code null} if it is not known
	 */
	public String getName() {
		return name;
	}

	/**
	 * Get the upper-left hand corner of this feature as presently displayed.
	 * Return Integer.MAX_VALUE if this feature has no visible representation.
	 * 
	 * @return the x coordinate
	 */
	public abstract int getX();

	/**
	 * Get the upper-left hand corner of this feature as presently displayed
	 * Return Integer.MAX_VALUE if this feature has no visible representation.
	 * 
	 * @return the y coordinate
	 **/
	public abstract int getY();

	/**
	 * Returns a string representation of the object.
	 * 
	 * @return a developer-friendly description of this feature, for debugging
	 */
	@Override
	public String toString() {
		return super.toString() + "(" + name + ")";
	}

	/**
	 * Initialize this feature. This is called on show initialization. A show
	 * will initialize all of its features after it initializes the segments.
	 * Clients of the GRIN framework should never call this method directly.
	 * Custom feature extensions must implement this method.
	 */
	protected abstract void initialize();

	/**
	 * Free any resources held by this feature. It is the opposite of
	 * initialize().
	 * <p>
	 * Features are deactivated before they are destroyed. For example, the last
	 * segment a show is in when the show is destroyed will probably be active,
	 * but it is deactivated and unsetup() is called in Show.destroy() before
	 * the features are destroyed.
	 * <p>
	 * Cloned features are handled a little differently. They have destroy()
	 * called from within Feature.destroyClonedSubgraph(). Since cloned features
	 * are exempt from the setup/unsetup lifecycle, the cloned feature will not
	 * be activated, but will be in a setup state when destroy() is called.
	 * Features that support cloning must therefore release any resources they
	 * acquired in createClone() or initializeClone() during the call to
	 * destroy().
	 * <p>
	 * Clients of the GRIN framework should never call this method directly.
	 * Custom feature extensions must implement this method.
	 */
	protected abstract void destroy();

	/**
	 * Change the setup mode of this feature. The new mode will always be
	 * different than the old. Custom feature extensions must implement this
	 * method.
	 * <p>
	 * This method must return a guaranteed lower bound for the number of times
	 * it will send a feature setup command as a result of this call. That is,
	 * it must send at least as many feature setup commands to the segment as
	 * the number returned; sending an accurate number makes the setup process
	 * more efficient, since the time it takes to process a command scales with
	 * the number of features in a segment. When mode is {@code false},
	 * {@code 0} should be returned.
	 * 
	 * @param mode
	 *            the setup mode of this feature
	 */
	protected abstract int setSetupMode(boolean mode);

	/**
	 * Change the activated mode of this feature. The new mode will always be
	 * different than the old. Clients of the GRIN framework should never call
	 * this method directly. Custom feature extensions must implement this
	 * method.
	 * 
	 * @param mode
	 *            the activated mode of this feature
	 */
	protected abstract void setActivateMode(boolean mode);

	/**
	 * This is where the feature says whether or not it needs more setup. The
	 * implementation of this method must not call outside code or call any
	 * animation manager methods. For a given setup cycle, this method is called
	 * only after setup(). Clients of the GRIN framework should never call this
	 * method directly. Custom feature extensions must implement this method.
	 * 
	 * @return whether or not the feature needs more setup
	 */
	public abstract boolean needsMoreSetup();

	/**
	 * Called by the show when it's time to begin setting up this feature. This
	 * might be called from the show multiple times; each call will eventually
	 * be matched by a call to {@link #unsetup()}. Clients of the GRIN framework
	 * should never call this method directly, and it should not be overridden.
	 * 
	 * @see #unsetup()
	 * 
	 * @return a lower bound on the number of setup commands that will be
	 *         generated due to this call
	 */
	public final int setup() {
		setupCount++;
		if (setupCount == 1) {
			return setSetupMode(true);
		} else {
			return 0;
		}
	}

	/**
	 * Called by the show when this feature is no longer needed by whatever
	 * contains it. When the last call to {@link #setup()} has been matched by a
	 * call to {@link #unsetup()}, it's time to unload this feature's assets.
	 * 
	 * @see #setup()
	 */
	public final void unsetup() {
		setupCount--;
		if (setupCount == 0) {
			setSetupMode(false);
		}
	}

	/**
	 * Check to see if this feature has been set up, or has a pending request to
	 * be set up.
	 * 
	 * @return {@code true} if this feature has been set up, {@code false}
	 *         otherwise
	 */
	public final boolean isSetup() {
		return setupCount > 0;
	}

	/**
	 * Called by the show when this feature becomes activated, that is, when it
	 * starts presenting. These nest, so this can be called multiple times. When
	 * the last call to activate() is undone by a call to deactivate(), that
	 * means this feature is no longer being shown.
	 * 
	 * @see #deactivate()
	 */
	public final void activate() {
		activateCount++;
		if (activateCount == 1) {
			setActivateMode(true);
		}
	}

	/**
	 * Called by the show when this feature is no longer being presented by
	 * whatever contains it. Clients of the GRIN framework should never call
	 * this method directly, and it should not be overridden.
	 * 
	 * @see #activate()
	 */
	public final void deactivate() {
		activateCount--;
		if (activateCount == 0) {
			setActivateMode(false);
		}
	}

	private Command featureSetupCommand = null;

	/**
	 * When a feature finishes its setup, it should call this to tell the show
	 * about it. If this is done, then the feature should return the number of
	 * commands it can generate from {@link #setSetupMode(boolean)}.
	 */
	protected void sendFeatureSetup() {
		if (featureSetupCommand == null) {
			featureSetupCommand = new Command(show) {
				@Override
				public void execute() {
					Segment s = show.getCurrentSegment();
					if (s != null) {
						s.runFeatureSetup();
					}
				}
			};
		}
		show.runCommand(featureSetupCommand);
	}

	/**
	 * Paint the current state of this feature to {@code gc}. Clients of the
	 * GRIN framework should not call this method directly.
	 * 
	 * @param gc
	 *            the place to paint to
	 */
	public abstract void paintFrame(Graphics gc);

	/**
	 * Called from Segment to advance us to the state we should be in for the
	 * next frame.
	 */
	public abstract void nextFrame();

	/**
	 * Called from the {@link ResetFeatureCommand}, this should reset the
	 * internal state of the feature to what it was when first activated.
	 */
	public void resetFeature() {
		if (activateCount > 0) {
			setActivateMode(false);
			setActivateMode(true);
		}
	}

	/**
	 * Clone the subgraph of a scene graph, rooted at this feature. This node,
	 * any child nodes, and other children below this node in the scene graph
	 * are copied. The original feature must have been initialized and setup
	 * must have completed, and it must not currently be activated (that is,
	 * visible). The cloned feature will not be subject to the setup/unsetup
	 * lifecycle. The feature being cloned might be activated or not.
	 * <p>
	 * The resulting cloned feature will already be initialized, but the caller
	 * is responsible for ensuring that {@link #destroyClonedSubgraph()} is
	 * called on the top node of all cloned feature subgraphs.
	 * <p>
	 * A cloned feature can be used by adding it to the set of visible features
	 * in a group using {@link Group.resetVisibleFeatures()} TODO.
	 * <p>
	 * A scene graph can contain a reference to another feature, either by a
	 * direct reference, like the model attached to a Translator, or with a
	 * command that refers to a feature, like an activate_part command directed
	 * to an assembly. In either case, if the referred-to feature is in the
	 * subgraph being cloned, then the clone is what will be referred to. If the
	 * referred-to feature is outside the subgraph being cloned, then the
	 * original feature will be referred to. It's the caller's responsibility to
	 * ensure that this results in a scene graph that gives valid behavior;
	 * usually this means that any referred-to feature that carries state (like
	 * a translation's model) should be in the set of features being cloned.
	 * 
	 * @param clones
	 *            A map from original feature to its clone, which should
	 *            initially be empty. When this method completes it will be
	 *            populated with all cloned features, including the top node of
	 *            the cloned subgraph. You can use this map to locate the clones
	 *            of features in your graph, e.g. named features that you've
	 *            previously looked up. This must not be null. The key of each
	 *            entry is the original feature, and the value is its clone.
	 * 
	 * @throws UnsupportedOperationException
	 *             if this feature's class doesn't implement cloneFeature(), or
	 *             if any features or commands this feature refers to doesn't
	 *             implement cloning. All built-in GRIN commands and features
	 *             features do implement cloning, but extension subclasses might
	 *             not. The implementation of cloneFeature() in the Feature
	 *             superclass always throws this exception.
	 * 
	 * @throws IllegalStateException
	 *             may be thrown if the feature has not been initialized and set
	 *             up, if the feature is currently activated (that is, visible),
	 *             or if the feature has been destroyed.
	 * 
	 * @see com.hdcookbook.grin.features.Group#resetVisibleParts(com.hdcookbook.grin.Feature[])
	 * @see #initialize()
	 * @see #destroyClonedSubgraph()
	 */
	public final Feature cloneSubgraph(Map<Feature, Feature> clones) {
		// TODO enable: assert clones.isEmpty();
		Feature result = makeNewClone(clones);
		// TODO enable: assert clones.get(this) != null;
		for (Map.Entry<Feature, Feature> entry : clones.entrySet()) {
			Feature key = entry.getKey();
			Feature value = entry.getValue();
			value.initializeClone(key, clones);
		}
		return result;
	}

	/**
	 * This is an implementation method that should not be called directly by
	 * applications; applications should call {@link #cloneSubgraph(Map)}.
	 * <p>
	 * Make a clone of this feature if a new one is required, that is, if it
	 * hasn't already been cloned. This method checks to see if this feature has
	 * already been cloned, and if so, returns the previous clone. Otherwise, it
	 * calls createClone() to create that clone, then adds it into the clones
	 * map.
	 * <p>
	 * See the documentation of {@link #cloneSubgraph(Map)} for a list of
	 * runtime exceptions this method can throw.
	 * 
	 * @param clones
	 *            a map from original feature to cloned feature (the entries are
	 *            added by {@link #makeNewClone(Map)}
	 * @throws UnsupportedOperationException
	 *             as specified in {@link #cloneSubgraph(Map)}
	 * @throws IllegalStateException
	 *             as specified in {@link #cloneSubgraph(Map)}
	 * 
	 * @see #cloneSubgraph(Map)
	 * @see #createClone(Map)
	 */
	public final Feature makeNewClone(Map<Feature, Feature> clones) {
		Feature clone = clones.get(this);
		if (clone == null) {
			clone = createClone(clones);
			assert clones.get(this) == null;
			// A well-formed scene graph is a directed acyclic graph, so
			// if one of our children added us, the scene graph is
			// not well-formed.
			clones.put(this, clone);
		}
		return clone;
	}

	/**
	 * This is an implementation method that should not be called directly by
	 * applications; applications should call {@link #cloneSubgraph(Map)}. New
	 * subclasses of {@code Feature} that wish to support cloning must override
	 * this method.
	 * <p>
	 * Create a new clone of this feature. This method creates a new instance of
	 * this feature, and creates new instances of any sub-features, but it does
	 * not initialize the feature. This method is called from
	 * {@link #makeNewClone(Map)}.
	 * <p>
	 * See the documentation of {@link #cloneSubgraph(Map)} for a list of
	 * runtime exceptions this method can throw. Subclasses that wish to support
	 * cloning must override this method.
	 * 
	 * @param clones
	 *            a map from original feature to cloned feature (entries are
	 *            added by {@link #makeNewClone(Map)}
	 * @throws UnsupportedOperationException
	 *             as specified in {@link #cloneSubgraph(Map)}
	 * @throws IllegalStateException
	 *             as specified in {@link #cloneSubgraph(Map)}
	 * 
	 * @see #makeNewClone(Map)
	 * @see #cloneSubgraph(Map)
	 **/
	protected Feature createClone(Map<Feature, Feature> clones) {
		throw new UnsupportedOperationException(getClass().getName()
				+ ".createClone()");
	}

	/**
	 * This is an implementation method that should not be called directly by
	 * applications; it is called from {@link #cloneSubgraph(Map)}. New
	 * subclasses of {@code Feature} may override this method.
	 * <p>
	 * Initialize this cloned feature from its original. This is called after
	 * the entire subgraph has been cloned, so the map containing the set of
	 * clones will be complete. See the documentation of
	 * {@link #cloneSubgraph(Map)} for a list of runtime exceptions this method
	 * can throw. Subclasses that wish to support cloning must override this
	 * method.
	 * <p>
	 * If this feature doesn't need initialization it's OK for a feature to not
	 * implement it; the default version of this method does nothing. Typically,
	 * you only need to implement this for features that have references to
	 * other features that aren't sub-features, or that have commands that might
	 * have references to other features.
	 * 
	 * @see #cloneSubgraph(java.util.HashMap)
	 * 
	 * @param original
	 * @param clones
	 */
	protected void initializeClone(Feature original,
			Map<Feature, Feature> clones) {
	}

	/**
	 * Destroy a subgraph of features that was created with
	 * {@link #cloneSubgraph(Map)}. Application code that creates new features
	 * using {@link #cloneSubgraph(Map)} must destroy the set of cloned features
	 * by calling this method on the top node of each cloned subgraph. In other
	 * words, every call to {@link #cloneSubgraph(Map)} should be balanced by a
	 * call to {@link #destroyClonedSubgraph()}. This should be done when the
	 * cloned subgraph is not part of any group, but before the containing show
	 * is destroyed.
	 * <p>
	 * This method will cause {@link #destroy()} to be called on every feature
	 * that's a member of the cloned subgraph.
	 * 
	 * @see #cloneSubgraph(Map)
	 */
	public final void destroyClonedSubgraph() {
		Set<Feature> set = new HashSet<Feature>();
		addSubgraph(set);
		for (Feature f : set) {
			f.destroy();
		}
	}

	/**
	 * This is an implementation method that is not intended to be called
	 * directly by applications. New subclasses of {@code Feature} may override
	 * this method, and must override it if they have children.
	 * <p>
	 * Add this node and all of its descendant nodes to the given set. The
	 * superclass definition of this method adds the current node. Any node
	 * types that have children should override this method to call the
	 * superclass version, then recursively invoke this method on each child.
	 * 
	 * @param set
	 */
	public void addSubgraph(Set<Feature> set) {
		set.add(this);
	}

	/**
	 * Return a reference to the feature {@code f} for use within a reference.
	 * If some other feature contains a reference to this feature (other than an
	 * "owning" parent-child reference), then this method will give a reference
	 * to a clone of the feature being called, if one was made, or if not, to
	 * this original feature.
	 * 
	 * @param f
	 *            The feature reference (this may be {@code null}).
	 **/
	protected static Feature clonedReference(Feature f,
			Map<Feature, Feature> clones) {
		if (f == null) {
			return null;
		}
		Object result = clones.get(f);
		if (result == null) {
			return f;
		} else {
			return (Feature) result;
		}
	}

	/**
	 * Clone a command array within a feature. This is used by some feature's
	 * implementation of {@link #makeNewClone(Map)}.
	 * 
	 * @param commands
	 *            the array to clone(may be {@code null}
	 * @param clones
	 *            a map from original feature to their clones
	 * @return the cloned command array
	 * @throws UnsupportedOperationException
	 *             as specified in Feature.cloneSubgraph()
	 * @throws IllegalStateException
	 *             as specified in Feature.cloneSubgraph()
	 */
	protected static Command[] cloneCommands(Command[] commands,
			Map<Feature, Feature> clones) {
		if (commands == null || commands.length == 0) {
			return commands;
		}
		Command[] result = new Command[commands.length];
		boolean changed = false;
		for (int i = 0; i < commands.length; i++) {
			result[i] = commands[i].cloneIfNeeded(clones);
			changed = changed || result[i] != commands[i];
		}
		return (!changed) ? commands : result;
	}
	
	/**
	 * Mark this feature as modified for the next call to
	 * {@link #paintFrame(Graphics)}. This can be called by a parent node on its
	 * children, e.g. by setting an alpha value.
	 * 
	 * @see #paintFrame(Graphics)
	 */
	public void markAsChanged() {
		changed = true;
	}
	
	/**
	 * TODO javadoc
	 * 
	 * @return
	 */
	public boolean isChanged() {
		return changed;
	}
	
	/**
	 * Here, we re-set {@code changed}.
	 */
	public void paintDone() {
		changed = false;
	}
}
